package com._119yn.host.gst200.cansocket;

import com._119yn.host.gst200.cansocket.codec.*;
import com._119yn.host.gst200.cansocket.handler.CanFrameHandler;
import com._119yn.host.gst200.cansocket.handler.KeepaliveHandler;
import com._119yn.host.gst200.constant.LogTags;
import com._119yn.host.gst200.properties.CanSocketProperties;
import com._119yn.host.gst200.service.DtuService;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;

import java.net.ConnectException;
import java.net.UnknownHostException;

@Slf4j
@Component
@RequiredArgsConstructor
public class CanSocketClient implements Runnable, ApplicationListener<ApplicationReadyEvent> {

    private final CanSocketProperties canSocketProperties;

    private final DtuService dtuService;

    @Override
    public void onApplicationEvent(@NonNull ApplicationReadyEvent event) {
        Thread mainThread = new Thread(this);
        mainThread.setName("CAN-Socket-Main");
        mainThread.setDaemon(true);
        mainThread.start();
    }

    @SneakyThrows
    @Override
    public void run() {
        while (true) {
            try (EventLoopGroup eventLoopGroup = new NioEventLoopGroup()) {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap
                        .group(eventLoopGroup)
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) {
                                ChannelPipeline pipeline = ch.pipeline();
                                // 添加空闲状态处理器，读或写操作在指定时间内未发生时触发事件
                                pipeline.addLast(new IdleStateHandler(60, 60, 120));
                                pipeline.addLast(new LineEncoder()); // 行协议编码器
                                pipeline.addLast(new LineDecoder()); // 行协议解码器
                                pipeline.addLast(new PingEncoder()); // ping心跳编码器
                                pipeline.addLast(new PongDecoder()); // pong心跳解码器
                                pipeline.addLast(new CanFrameDecoder()); // CanFrame解码器
                                pipeline.addLast(new CanFrameEncoder()); // CanFrame编码器
                                pipeline.addLast(new KeepaliveHandler()); // 保活处理器
                                pipeline.addLast(new CanFrameHandler(dtuService)); // CanFrame处理器
                            }
                        });
                ChannelFuture future = bootstrap.connect(canSocketProperties.getServer().getHost(), canSocketProperties.getServer().getPort()).sync();
                logInfo("连接成功");
                Channel channel = future.channel();
                channel.closeFuture().sync();
                logWarn("连接断开");
            } catch (Exception e) {
                handleException(e);
            }
            Thread.sleep(5000);
        }
    }

    private void handleException(Exception e) throws Exception {
        Throwable cause = e.getCause();
        if (cause == null) {
            switch (e) {
                case IllegalArgumentException ex -> {
                    if (ex.getMessage().startsWith("port out of range:")) {
                        logWarn("无效端口");
                    } else {
                        throw e;
                    }
                }
                case UnknownHostException ex -> {
                    if (ex.getMessage().contains("(") && ex.getMessage().contains(")")) {
                        logWarn("无效主机");
                    } else if (ex.getMessage().contains(".")) {
                        logWarn("无效主机");
                    } else {
                        throw e;
                    }
                }
                default -> throw e;
            }
        } else {
            switch (e.getCause()) {
                case ConnectException ex -> {
                    if (ex.getMessage().startsWith("Connection refused:")) {
                        logWarn("连接拒绝");
                    } else if (ex.getMessage().startsWith("Connection timed out:")) {
                        logWarn("连接超时");
                    } else {
                        throw e;
                    }
                }
                case IllegalArgumentException ex -> {
                    logWarn("参数无效");
                }
                default -> throw e;
            }
        }
    }

    void logInfo(String mark) {
        logInfo(mark, null);
    }

    void logWarn(String mark) {
        logWarn(mark, null);
    }

    void logInfo(String mark, String content, Object... objects) {
        if (content == null) {
            String tpl = "%s:[%s]: {}:{}".formatted(LogTags.getCanSocket(), mark);
            log.info(tpl, canSocketProperties.getServer().getHost(), canSocketProperties.getServer().getPort());
        } else {
            String tpl = "%s:[%s]: {}:{} ({})".formatted(LogTags.getCanSocket(), mark);
            log.info(tpl, canSocketProperties.getServer().getHost(), canSocketProperties.getServer().getPort(), content, objects);
        }
    }

    void logWarn(String mark, String content, Object... objects) {

        if (content == null) {
            String tpl = "%s:[%s]: {}:{}".formatted(LogTags.getCanSocket(), mark);
            log.warn(tpl, canSocketProperties.getServer().getHost(), canSocketProperties.getServer().getPort());
        } else {
            String tpl = "%s:[%s]: {}:{} ({})".formatted(LogTags.getCanSocket(), mark);
            log.warn(tpl, canSocketProperties.getServer().getHost(), canSocketProperties.getServer().getPort(), content, objects);
        }
    }

}
